
# This script has been tested with python 2.7.
# The Python Imaging Library (PIL) is used: http://www.pythonware.com/products/pil/
#
# Written for development on the Mikromedia board for PIC24.
#
#
# Input:
# An image file of type supported by your installation of PIL, in RGB format.
#
#
#
# Output:
# An image file in this format:
# 
# HEADER:
# number of columns (2 bytes)
# number of rows (2 bytes)
# encoding (2 bytes):
#   0 - no encoding
#   1 - RLE (run-length encoding)
#
# DATA:
#   no encoding: contiguous pixel values, 2 bytes each
#   RLE encoding: contiguous runlength/color pairs, in the format:
#     (2 bytes) runlength  :  (2 bytes) pixel value
#     This indicates that there are <runlength> pixels of the value <pixel value>.
#

import os, tkFileDialog, pickle
from Tkinter import *
import Image # http://www.pythonware.com/products/pil/

class App(Frame):
	def __init__(self, master=None):
		Frame.__init__(self, master)

		self.scale = StringVar()
		self.scale.set('100')
		self.scaleBoolVar = BooleanVar()

		self.METAFILE_NAME = '.stored_filename'
		
		self.pack()

		self.entryboxLabel = Label(text="input file:")
		self.entryboxLabel.pack()
		self.entrybox = Entry()
		self.entrybox.pack(expand=1, fill='x')
		
		self.scaleFactorLabel = Label(text="scale factor (max side length of output in pixels):")
		self.scaleFactorLabel.pack()
		self.scaleFactor = Entry()
		self.scaleFactor.pack(expand=1, fill='x')
		self.scaleFactor["textvariable"] = self.scale

		self.scaleBool = Checkbutton(text="scale image?", onvalue=True, offvalue=False, variable=self.scaleBoolVar)
		self.scaleBool.select()
		self.scaleBool.pack()

		self.filename = StringVar()
		if os.path.isfile(self.METAFILE_NAME):
			#load stored filename from the last time this program was used
			metafile = open(self.METAFILE_NAME, 'r')
			unpickler = pickle.Unpickler(metafile)
			self.filename.set(unpickler.load())
		else:
			#use default filename (program hasn't been used yet on this system)
			self.filename.set('choose file')
			
		self.entrybox["textvariable"] = self.filename

		self.getfilenamebutton = Button(self, text="Choose file", command=self.choosefile)
		self.getfilenamebutton.pack(side="left")

		self.gobutton = Button(self, text="Convert image", command=self.process_image)
		self.gobutton.pack(side="left")

		self.quitbutton = Button(self, fg="red", text="Quit", command=self.finish)
		self.quitbutton.pack(side="right")

	#load image, process it, and write out to a .mikropic file
	def process_image(self):
		self.load_and_check_image()

		#create file to store converted data
		self.sourcename = self.name_outfile( self.filename.get() )
		self.source = open(self.sourcename, 'wb')

		#scale image
		if self.scaleBoolVar.get():
			self.im.thumbnail((int(self.scale.get()), int(self.scale.get())), Image.ANTIALIAS)

		#write header
		#columns
		self.print_int_str( self.im.size[0] )
		#rows
		self.print_int_str( self.im.size[1] )

		self.pack_into_words()
		self.RLE_encode()
		#encoding
		self.print_int_str(self.encoding)
		self.write_file()
		self.source.close()
		self.Print_info()

		#print out some info for the user
	def Print_info(self):
		print "********************************"
		print "Output file: ", self.sourcename
		print "Image dimensions: ",  self.im.size.__str__()
		print "Compression scheme: ",
		if self.encoding == 0:
			print "None"
		elif self.encoding == 1:
			print "RLE (runlength encoding)"
		print "Image size: ", 2 * self.im.size[0] * self.im.size[1], " bytes"
		print "File size: ", 6 + 2 * len(self.imagedata), " bytes"

	def RLE_encode(self): # call pack_into_words() first.
		# temp buffer for encoded image; to be moved into self.imagedata
		self.encoded_image = [] 
		runlength = 0
		oldpixel = self.imagedata[0]
		for pixel in self.imagedata:
			if pixel != oldpixel:
				self.encoded_image.append(runlength)
				self.encoded_image.append(oldpixel)
				oldpixel = pixel
				runlength = 1
			else:
				runlength += 1
		# finish up with final run of pixels.
		self.encoded_image.append(runlength)
		self.encoded_image.append(oldpixel)
		if len(self.encoded_image) < len(self.imagedata):
			self.imagedata = self.encoded_image
			self.encoding = 1
		else:
		 	self.encoding = 0


	#load image and make sure it's a compatible format
	def load_and_check_image(self):
		self.im = Image.open( self.filename.get() )
		if self.im.mode != "RGB":
			print 'Image is in ' + self.im.mode + ' mode; must be RGB'
			return

	#arrange image data into words and store it in a list
	#TODO simultaneous compression 
	def pack_into_words(self):
		self.imagedata = [] # TODO: pre-allocate this list
		for pixel in list(self.im.getdata()):
			self.r = int(pixel[0])
			self.g = int(pixel[1])
			self.b = int(pixel[2])
			thispixel = self.pack_bits(self.r,self.g,self.b)
			self.imagedata.append(thispixel)

	#write out .mikropic file
	def write_file(self):
		for thispixel in self.imagedata:
			self.print_int_str( thispixel )

	#generate output file name
	def name_outfile(self, filename):
		return os.path.splitext(filename)[0] + ".mikropic"

	#open file explorer to select file
	def choosefile(self):
		self.filename.set(tkFileDialog.askopenfilename())

	#bit packing
	def pack_bits(self, r, g, b):
		return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3)
	
	#write 16 bits of binary data to file
	def print_int_str(self, word):
		self.source.write( "%c" % (word & 0xff) )
		self.source.write( "%c" % (word >> 8) )
	
	#save current filename for the future and quit
	def finish(self):
		stored_filename = open(self.METAFILE_NAME, 'w')
		pickler = pickle.Pickler(stored_filename)
		pickler.dump(self.filename.get())
		self.quit()

root = Tk()
root.title("MikroPictures Image Convertor")
app = App(master=root)
app.mainloop()
root.destroy()

